package com.sunxboy.platform.logging;

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;

/**
 * Burst filter all the incoming events according to rules.
 */
public class RegexpFilter extends Filter {
    protected final static Logger logger = Logger.getLogger(RegexpFilter.class);

    private Pattern stringToMatch;
    private int recoveryRate = 5;
    private int recoveryInterval = 5000;
    private int burstLimit = 10;
    private int discardReportThreshold = 1000;

    private Semaphore tokenCount = new Semaphore(10);
    private AtomicLong timeStamp = new AtomicLong(System.currentTimeMillis());
    private AtomicInteger discardCount = new AtomicInteger();

    @Override
    public int decide(LoggingEvent event) {
        if (stringToMatch.matcher(event.getRenderedMessage()).matches()) {
            do {
                long timeStampValue = timeStamp.get();
                long newTimeStampValue = System.currentTimeMillis();
                // don't try recover anything if period is less than expected
                if ((newTimeStampValue - timeStampValue) < recoveryInterval)
                    break;
                // try to recover if we can set timestamp to our value
                if (timeStamp.compareAndSet(timeStampValue, newTimeStampValue)) {
                    int available = tokenCount.availablePermits();
                    int delta = (int) (recoveryRate * (newTimeStampValue - timeStampValue) / (float) recoveryInterval);
                    delta = Math.min(burstLimit - available, delta);
                    tokenCount.release(delta);
                    break;
                }
            } while (true);

            if (!tokenCount.tryAcquire()) {
                int currentDiscardCount = discardCount.incrementAndGet();
                if (currentDiscardCount > discardReportThreshold && discardCount.compareAndSet(currentDiscardCount, 0)) {
                    logger.error("Discarded " + currentDiscardCount + " messages similar to " + event.getRenderedMessage());
                }
                return Filter.DENY;
            }
        }
        return Filter.NEUTRAL;
    }

    public void setRegexpToMatch(String stringToMatch) {
        this.stringToMatch = Pattern.compile(stringToMatch, Pattern.DOTALL);
    }

    public int getRecoveryRate() {
        return recoveryRate;
    }

    public void setRecoveryRate(int recoveryRate) {
        this.recoveryRate = recoveryRate;
    }

    public int getRecoveryInterval() {
        return recoveryInterval;
    }

    public void setRecoveryInterval(int recoveryInterval) {
        this.recoveryInterval = recoveryInterval;
    }

    public int getBurstLimit() {
        return burstLimit;
    }

    public void setBurstLimit(int burstLimit) {
        tokenCount.drainPermits();
        tokenCount.release(burstLimit);
        this.burstLimit = burstLimit;
    }
}
